/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: cbus_map.c
 *
 * Purpose: implementation a hash map for cbus endpoint
 *
 * Developer:
 *   wen.gu , 2022-02-16
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/

#include "cbus/core/cbus_map.h"

#include <string.h>

#include "cbus/core/cbus_log.h"
/******************************************************************************
 **    MACROS
 ******************************************************************************/
#define isUseReservoir(size) ((size) <= sizeof(GPTR))

#define MAP_CAPACITY_DEFAULT (1 << 4 /*16*/)
#define MAP_REHASH_FACTOR (0.75f)  /** todo refine me?? */

#define MAP_CAPACITY_MAX  1 << 30 /*1 073 741 824 */

//#define INTER_MAX_VALUE  1<<31  /*int max */

#define MAP_ITEM_STORAGE(item) isUseReservoir((item)->size) ? &(item)->u.reservoir : (item)->u.ext_data
/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/
typedef GFLT value_reservoir_t;


/**data structure for range value */
typedef struct cbus_map_range_s
{
	GS32 start;
	GS32 end;
}cbus_map_range_t;

/** data structure for rect value */
typedef struct cbus_map_rect_s
{
	GS32 left;
	GS32 top;
	GS32 right;
	GS32 bottom;
}cbus_map_rect_t;

/**date structure for map item */
typedef struct _cbus_map_item
{	
    /**the value info of key-value */
	GU32 size;

	struct _cbus_map_item* next;

	/** the key of key-value */
	GU64 key; 


    GPTR value; /** if the size <= sizeof(value) then just '=', else (> sizeof(value)) do alloc ) */
    
    pointer_free_func on_free; /** a callback to free pointer data type */
}cbus_map_item_t;

struct _cbus_map
{
    GU32 value_type;
    cbus_map_item_t** table;
    GU32 size;
    GU32 size_mask; /** aways == 'size' - 1 */    
    GU32 used;
    GU32 threshold; /** if used >= threshold, then need rehash the current map */
};


/******************************************************************************
 **    inner FUNCTION DEFINITIONS
 ******************************************************************************/

static void update_item_value(cbus_map_item_t* item, const void* data, GU32 size)
{
    if (isUseReservoir(size))
    {
        item->value = (void*)data;
    } 
    else
    {
        memcpy(item->value, data, size);
    }
}

static void get_item_value(cbus_map_item_t* item,  void** data, GU32* size)
{
    *size = item->size;
    
    if ((isUseReservoir(item->size)))
	{
		*data = item->value;
	}
	else
	{
		memcpy(*data, item->value, item->size);
	}
}

static cbus_map_item_t* map_item_new(GU64 key, const void* data, GU32 size, pointer_free_func on_free)
{
    if (!data || (size == 0))
    {
        return NULL;
    }

    GU32 alloc_size = sizeof(cbus_map_item_t);

    if (!isUseReservoir(size))
    {
        alloc_size += size;
    }   

    cbus_map_item_t* item = (cbus_map_item_t*)malloc(alloc_size);

    if (item)
    {
        item->key = key;
        item->size = size;
        item->next = NULL;
        item->on_free = on_free;
        if (isUseReservoir(size))
        {
            item->value = (void*)data;
        }
        else
        {
            item->value = (void *)(((GU08*)item) + sizeof(cbus_map_item_t));
            memcpy(item->value, data, size);
        }          
    }

    return item;
}

static inline GU32 map_get_index(GU32 table_size_mask, GU64 key)
{
    return key & table_size_mask;
}


static void map_hash_move(cbus_map_item_t** new_table, GU32 new_table_size_mask, cbus_map_item_t** old_table, GU32 old_table_size)
{
    for (size_t i = 0; i < old_table_size; i++)
    {
        cbus_map_item_t* item = old_table[i];

        if (item)
        {
            cbus_map_item_t* next = NULL;
            do
            {
                GU32 idx = map_get_index(new_table_size_mask, item->key);
                next = item->next;
                item->next = new_table[idx];
                new_table[idx] = item;
                item = next;
            } while (item);            
        }
    }
}

static GErrc map_rehash(cbus_map_t map)
{
    if (map->size == MAP_CAPACITY_MAX)
    {
        map->threshold = GS32_MAX;
        return G_OK;
    }

    GU32 size = map->size * 2;

    if (size > MAP_CAPACITY_MAX)
    {
        size = MAP_CAPACITY_MAX;
    }

    GU32 alloc_size = size * sizeof (cbus_map_item_t*);

    cbus_map_item_t** new_table = (cbus_map_item_t**)malloc(alloc_size);

    if (!new_table)
    {
        return G_ErrInsufficientResources;
    }

    memset(new_table, 0, alloc_size);
    GU32 table_size_mask = size - 1;

    map_hash_move(new_table, table_size_mask, map->table, map->size);

    free(map->table);
    map->table = new_table;
    map->size = size;
    map->size_mask = size -1;
    map->threshold = (GU32)(size * MAP_REHASH_FACTOR);

    return G_OK;
}


static GErrc hash_set(cbus_map_t map, GU64 key, GU32 type, const void* data, GU32 size, pointer_free_func on_free)
{

    if (type != map->value_type)
    {
        LOGE("unsupported value type(0x%08x), map value type is: 0x%08x\n", type, map->value_type);
        return G_ErrUndefined;
    }
    //LOGI("[hash set] key: %llu, value: %p\n", key, data);
    GU32 idx = map_get_index(map->size_mask, key); //calculate index in the hash bucket

    cbus_map_item_t* item = map->table[idx];
    cbus_map_item_t* tail = NULL;
    while (item)
    {
        if (item->key == key)
        {/** if current key i exist, then just update the value */
           if (item->on_free && (map->value_type == CMV_TYPE_Pointer)) 
            {
                item->on_free(item->value); /** refine me??  check isUseReservoir ?? */
            }

            if (item->size != size) /** the size of data changed, then need free old item and alloc new item */
            {
                free(item);
                break; /** todo refine me?? */
            }

            update_item_value(item, data, size);
            return G_OK;
        }

        tail = item;
        item = item->next;
    }

    cbus_map_item_t* temp = map_item_new(key, data, size, on_free);

    if (tail)
    {
        tail->next = temp;
    }
    else
    {
        map->table[idx] = temp;
    }

    map->used++;  

    if (map->used > map->threshold)
    {
        return map_rehash(map);
    }

    return G_OK;
}




static GErrc hash_get(cbus_map_t map, GU64 key, void** data, GU32* size)
{
    GU32 idx = map_get_index(map->size_mask, key); //calculate index in the hash bucket

    cbus_map_item_t* item = map->table[idx];

    while (item)
    {
        if (item->key == key)
        {
            get_item_value(item, data, size);
            //LOGI("[hash_get] key: %llu, value: %p\n", key, *data);
            return G_OK;    
        }

        item = item->next;
    }    

    return G_ErrNotFound;
}


static GErrc hash_delete(cbus_map_t map, GU64 key)
{
    
    GU32 idx = map_get_index(map->size_mask, key); //calculate index in the hash bucket

    cbus_map_item_t* item = map->table[idx];
    cbus_map_item_t* prev = item;
    while (item)
    {
        if (item->key == key)
        {
            if (item == prev) /** mean first item */
            {
                map->table[idx] = item->next;
            }
            else
            {
                prev->next = item->next;
            }

           if (item->on_free && (map->value_type == CMV_TYPE_Pointer)) 
            {
                item->on_free(item->value); /** refine me??  check isUseReservoir ?? */
            }

            free(item);
            return G_OK;
        }
        prev = item;
        item = item->next;
    }

    return G_ErrNotFound;
}

static void hash_clear(cbus_map_t map)
{
    for (size_t i = 0; i < map->size; i++)
    {
        cbus_map_item_t* item = map->table[i];
        cbus_map_item_t* next = NULL;
        while (item)
        {
            next = item->next;
            if (item->on_free && (map->value_type == CMV_TYPE_Pointer)) 
            {
                item->on_free(item->value); /** refine me??  check isUseReservoir ?? */
            }
            free(item);
            item = next;
        }

        map->table[i] = NULL;
    }    

    map->used = 0;
}


/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/

/**
*@brief  create a map data instance
*
*@param  map [out] return the map data instance 
*
*@return success:G_OK, fail: error code.
*@see
*/
cbus_map_t cbus_map_create(GU32 value_type, GU32 default_capacity) /** user set a default size for map item count, if 0,will be use inner default value,maybe 16? */
{

    if (value_type == CMV_TYPE_None)
    {
        return NULL;
    }

    cbus_map_t cm = (cbus_map_t)malloc(sizeof(struct _cbus_map));

    if (!cm)
    {
        return cm;
    }

    GU32 alloc_item_cnt = (default_capacity != CBUS_MAP_DEFAULT_CAPACITY) ? default_capacity : MAP_CAPACITY_DEFAULT;

    GU32 alloc_size = sizeof(cbus_map_item_t*) * alloc_item_cnt;

    cm->table = (cbus_map_item_t**)malloc(alloc_size);

    if (!cm->table)
    {
        free(cm);
        return NULL;
    }

    memset(cm->table, 0, alloc_size);

    cm->size = alloc_item_cnt;
    cm->size_mask = alloc_item_cnt - 1;
    cm->used = 0;
    cm->threshold = (GU32)(alloc_item_cnt * MAP_REHASH_FACTOR);
    cm->value_type = value_type;

    return cm;
}

/**
*@brief  destroy a map data instance
*
*@param  map [in] the map data instance 
*
*@return none.
*@see
*/
void cbus_map_destroy(cbus_map_t map)
{
    if (map)
    {
        hash_clear(map);
        free(map);
    }
}

/**
*@brief  clear all item in map data
*
*@param  map [in] the map data instance 
*
*@return None.
*@see
*/
void cbus_map_clear(cbus_map_t map)
{
    if (map)
    {
        hash_clear(map);
    }
}

/**
*@brief  remove a item with the key
*
*@param  map [in] the map data instance
*@param  key  [in] the key of item
*
*@return success: GTRUE, fail:GFALSE.
*@see
*/
GBOL cbus_map_remove(cbus_map_t map, GU64 key)
{
    if (map)
    {
        return (G_OK == hash_delete(map, key)) ? GTRUE : GFALSE;
    }

    return GFALSE;
}

GBOL cbus_map_for_each(cbus_map_t map, cbus_map_on_for_each_func on_for_each, GPTR opaque)
{
    if (map && on_for_each && map->table)
    {
        GU32 value_type = map->value_type;
        for (GU32 i = 0; i < map->size; i++)
        {
            cbus_map_item_t* item = map->table[i];
            while(item)
            {
                on_for_each(opaque, item->key, value_type, item->value, item->size);
                item = item->next;                
            }
        } 
        return GTRUE;       
    }

    return GFALSE;
}

GBOL cbus_map_for_each2(cbus_map_t map, cbus_map_on_for_each_func2 on_for_each, GPTR opaque)
{
    if (map && on_for_each && map->table)
    {
        GU32 value_type = map->value_type;
        for (GU32 i = 0; i < map->size; i++)
        {
            cbus_map_item_t* item = map->table[i];
            while(item)
            {
                if (on_for_each(opaque, item->key, value_type, item->value, item->size) == GFALSE)
                {
                    return GFALSE;
                }
                item = item->next;                
            }
        }   

        return GTRUE;
    }

    return GFALSE;
}

/**
*@brief  set data to map data
*
*@param  map [in] the map data instance
*@param  key  [in] the key of item
*@param  type [in] the type of item
*@param  data [in] data pointer
*@param  size [in] data size
*
*@return success: GTRUE, fail:GFALSE.
*@see
*/
GBOL cbus_map_set_data(cbus_map_t map, GU64 key, GU32 type, const void* data, GU32 size, pointer_free_func on_free)
{
    if (map)
    {
        return (G_OK == hash_set(map, key, type, data, size, on_free)) ? GTRUE : GFALSE;
    }

    return GFALSE;    
}

/**
*@brief  find data from map data
*
*@param  map [in]  the map data instance
*@param  key  [in]  the key of item
*@param  type [out] the type of item
*@param  data [out] data pointer
*@param  size [out] data size
*
*@return success: GTRUE, fail:GFALSE.
*@see
*/
GBOL cbus_map_find_data(cbus_map_t map, GU64 key, GU32* type, const void** data, GU32* size)
{
    if (map)
    {
        *type = map->value_type;
        return (G_OK == hash_get(map, key, (void**)data, size)) ? GTRUE : GFALSE;
    }
    return GFALSE;
}
//////////////////////////////////////////////////////////////////////////

#define CBUS_MAP_SET_API_IMPL(func, cmv_type, value_type) GBOL cbus_map_set_##func(cbus_map_t map, GU64 key, value_type value) \
{ return cbus_map_set_data(map, key, cmv_type, (const void*)&value, sizeof(value), NULL); }


CBUS_MAP_SET_API_IMPL(int32, CMV_TYPE_Int32, GS32)
CBUS_MAP_SET_API_IMPL(int64, CMV_TYPE_Int64, GS64)
CBUS_MAP_SET_API_IMPL(float, CMV_TYPE_Float, GFLT)
CBUS_MAP_SET_API_IMPL(boolean, CMV_TYPE_Boolean, GBOL)

GBOL cbus_map_set_pointer(cbus_map_t map, GU64 key, const void* value,  void(*pointer_free)(void* ptr))
{
    return cbus_map_set_data(map, key, CMV_TYPE_Pointer, (const void*)value, sizeof(value), pointer_free);
}

/**
*@brief  set a string to map data
*
*@param  map  [in] the map data instance
*@param  key   [in] the key of item
*@param  value [in] the value of item
*
*@return success: GTRUE, fail:GFALSE.
*@see
*/
GBOL cbus_map_set_string(cbus_map_t map, GU64 key, const char* value)
{
    return cbus_map_set_data(map, key, CMV_TYPE_String, (const void*)value, strlen(value) + 1, NULL);
}

/**
*@brief  set a rectangle to map data
*
*@param  map  [in] the map data instance
*@param  left  [in] left vertices
*@param  top   [in] top vertices
*@param  right [in] right vertices
*@param bottom [in] bottom vertices
*
*@return success: GTRUE, fail:GFALSE.
*@see
*/
GBOL cbus_map_set_rect(cbus_map_t map, GU64 key, GS32 left, GS32 top, GS32 right,GS32 bottom)
{
	cbus_map_rect_t rect;
	rect.left = left;
	rect.top = top;
	rect.right = right;
	rect.bottom = bottom;
	return cbus_map_set_data(map, key, CMV_TYPE_Rect, &rect, sizeof(rect), NULL);    
}

/**
*@brief  set a range value to map data
*
*@param  map  [in] the map data instance
*@param  key   [in] the key of item
*@param  start [in] range start
*@param  end   [in] end of range
*
*@return success: GTRUE, fail:GFALSE.
*@see
*/
GBOL cbus_map_set_range(cbus_map_t map, GU64 key, GS32 start, GS32 end)
{
	cbus_map_range_t range;
	range.start =start;
	range.end = end;
	return cbus_map_set_data(map, key, CMV_TYPE_Range, &range, sizeof(range), NULL);    
}


GBOL cbus_map_set_struct(cbus_map_t map, GU64 key, const void* struct_addr, GU32 struct_size)
{
    return cbus_map_set_data(map, key, CMV_TYPE_Struct, struct_addr, struct_size, NULL);
}
//////////////////////////////////////////////////////////////////////////

#define CBUS_MAP_GET_API_IMPL(func, cmv_type, value_type) GBOL cbus_map_find_##func(cbus_map_t map, GU64 key, value_type* value) \
{ \
	GU32 type = 0; 	const void *data; GU32 size = 0; \
	if ((cbus_map_find_data(map, key, &type, &data, &size)) && (type == cmv_type)){ \
		*value = *((value_type*)data); \
		return GTRUE; \
	} \
    return GFALSE; \
}


CBUS_MAP_GET_API_IMPL(int32, CMV_TYPE_Int32, GS32)
CBUS_MAP_GET_API_IMPL(int64, CMV_TYPE_Int64, GS64)
CBUS_MAP_GET_API_IMPL(float, CMV_TYPE_Float, float)
CBUS_MAP_GET_API_IMPL(boolean, CMV_TYPE_Boolean, GBOL)
//CBUS_MAP_GET_API_IMPL(pointer, CMV_TYPE_Pointer, const void*)
CBUS_MAP_GET_API_IMPL(string, CMV_TYPE_String, const char*)

GBOL cbus_map_find_pointer(cbus_map_t map, GU64 key, const void** value)
{
	GU32 type = 0;
	const void *data;
	GU32 size = 0;

	if ((cbus_map_find_data(map, key, &type, &data, &size) == GTRUE) && (type == CMV_TYPE_Pointer))
	{
        *value = data;
		return GTRUE;
	}

	return GFALSE;    
}

/**
*@brief  find a rectangle from map data
*
*@param  map  [in] the map data instance
*@param  key   [in]  the key of item
*@param  left  [out] left vertices
*@param  top   [out] top vertices
*@param  right [out] right vertices
*@param bottom [out] bottom vertices 
*
*@return success: GTRUE, fail:GFALSE.
*@see
*/
GBOL cbus_map_find_rect(cbus_map_t map, GU64 key, GS32* left, GS32* top, GS32* right,GS32* bottom)
{
	GU32 type = 0;
	const void *data;
	GU32 size = 0;

	if ((cbus_map_find_data(map, key, &type, &data, &size)) && (type == CMV_TYPE_Rect))
	{
		cbus_map_rect_t* rect = (cbus_map_rect_t*)data;
		*left = rect->left;
		*top = rect->top;
		*right = rect->right;
		*bottom = rect->bottom;
		return GTRUE;
	}

	return GFALSE;    
}

/**
*@brief  find a range value from map data
*
*@param  map  [in]  the map data instance
*@param  key   [in]  the key of item
*@param  start [out] range start
*@param  end   [out] end of range
*
*@return success: GTRUE, fail:GFALSE.
*@see
*/
GBOL cbus_map_find_range(cbus_map_t map, GU64 key, GS32* start, GS32* end)
{
	GU32 type = 0;
	const void *data;
	GU32 size = 0;

	if ((cbus_map_find_data(map, key, &type, &data, &size)) && (type == CMV_TYPE_Range))
	{
		cbus_map_range_t* range = (cbus_map_range_t*)data;
		*start = range->start;
		*end   = range->end;
		return GTRUE;
	}

	return GFALSE;    
}

GBOL cbus_map_find_struct(cbus_map_t map, GU64 key, void* struct_addr)
{
	GU32 type = 0;
	const void *data;
	GU32 size = 0;

	if ((cbus_map_find_data(map, key, &type, &data, &size)) && (type == CMV_TYPE_Struct))
	{
        memcpy(struct_addr, data, size);
		return GTRUE;
	}

	return GFALSE;    
}